import { RGBELoader } from '@amcax/base';
import { useAsyncEffect } from 'ahooks';
import React, { useEffect, useRef } from 'react';
import {
  ACESFilmicToneMapping,
  AmbientLight,
  DirectionalLight,
  EquirectangularReflectionMapping,
  Mesh,
  MeshPhysicalMaterial,
  MeshStandardMaterial,
  PerspectiveCamera,
  PMREMGenerator,
  Scene,
  SphereGeometry,
  sRGBEncoding,
  Texture,
  TextureLoader,
  WebGLRenderer,
} from 'three-legacy';

type TextureFile = {
  key: string;
  url: string;
};
interface MaterialPreviewProps {
  name: string;
  baseColor: string;
  metalness: number;
  roughness: number;
  textureFiles: Array<TextureFile>;
}

export const MATERIAL_PREVIEW_DEFAULT_PROPS: MaterialPreviewProps = {
  name: '',
  baseColor: '#D7D7D7',
  metalness: 1.0,
  roughness: 0.4,
  textureFiles: [],
};
export const MaterialPreview: React.FC<MaterialPreviewProps> = ({
  name = MATERIAL_PREVIEW_DEFAULT_PROPS.name,
  baseColor = MATERIAL_PREVIEW_DEFAULT_PROPS.baseColor,
  metalness = MATERIAL_PREVIEW_DEFAULT_PROPS.metalness,
  roughness = MATERIAL_PREVIEW_DEFAULT_PROPS.roughness,
  textureFiles = MATERIAL_PREVIEW_DEFAULT_PROPS.textureFiles,
}) => {
  const containerRef = useRef<HTMLDivElement>();
  const rendererRef = useRef<{
    scene: Scene;
    camera: PerspectiveCamera;
    renderer: WebGLRenderer;
    material: MeshStandardMaterial;
    sphere: Mesh;
    animationId: number;
  }>();

  // 初始化
  useAsyncEffect(async () => {
    if (!containerRef.current) return null;

    const container = containerRef.current;
    const width = container.clientWidth;
    const height = container.clientHeight;

    const scene = new Scene();

    const camera = new PerspectiveCamera(30, width / height, 0.1, 100);
    camera.position.set(2.5, 2, 2.5);
    camera.lookAt(0, 0, 0);

    const ambient = new AmbientLight(0xffffff, 0.4);
    const directional = new DirectionalLight(0xffffff, 1);
    directional.position.set(5, 5, 5);
    scene.add(ambient, directional);

    let material: MeshStandardMaterial | MeshPhysicalMaterial;
    if (name === 'glass') {
      // 玻璃
      material = new MeshPhysicalMaterial({
        color: baseColor,
        metalness,
        roughness,
        transmission: 1.0,
        ior: 1.5,
        transparent: true,
        envMapIntensity: 1.0,
      });
    } else {
      const files = textureFiles;
      const loader = new TextureLoader();
      let textures: Record<string, Texture> = {};

      if (Array.isArray(textureFiles) && files.length > 0) {
        const entries: [string, Texture][] = [];

        for (const { key, url } of textureFiles) {
          try {
            const texture = await loader.loadAsync(url);
            if (key === 'map') {
              texture.encoding = sRGBEncoding;
            }
            entries.push([key, texture]);
          } catch (err) {
            console.warn(`❌ 加载贴图失败: ${key} - ${url}`, err);
          }
        }

        textures = Object.fromEntries(entries);
      }

      material = new MeshStandardMaterial({
        ...textures,
        color: baseColor,
        metalness: metalness,
        roughness: roughness,
        envMapIntensity: 1.0,
      });
    }

    const geometry = new SphereGeometry(1, 64, 64);
    const sphere = new Mesh(geometry, material);
    scene.add(sphere);

    const renderer = new WebGLRenderer({
      antialias: true,
      alpha: true,
      preserveDrawingBuffer: true,
    });
    renderer.setSize(width, height);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setClearColor(0x000000, 0);
    container.appendChild(renderer.domElement);

    setupIBL(renderer, scene);
    renderer.outputEncoding = sRGBEncoding;
    renderer.physicallyCorrectLights = true;
    renderer.toneMapping = ACESFilmicToneMapping;
    renderer.toneMappingExposure = 1.2;

    const animate = () => {
      renderer.render(scene, camera);
      rendererRef.current!.animationId = requestAnimationFrame(animate);
    };

    rendererRef.current = {
      scene,
      camera,
      renderer,
      material,
      sphere,
      animationId: requestAnimationFrame(animate),
    };
  }, []);

  // 同步清理
  useEffect(() => {
    return () => {
      if (rendererRef.current) {
        const { sphere, renderer, material } = rendererRef.current;
        // 清理纹理
        if (material instanceof MeshStandardMaterial) {
          Object.values(material).forEach((value) => {
            if (value instanceof Texture) {
              value.dispose();
            }
          });
        }
        sphere?.geometry?.dispose();
        material?.dispose();
        renderer?.dispose();
        rendererRef.current.animationId &&
          cancelAnimationFrame(rendererRef.current.animationId);
      }
    };
  }, []);

  useEffect(() => {
    if (rendererRef.current) {
      const mat = rendererRef.current.material;
      mat.color.set(baseColor);
      mat.metalness = metalness;
      mat.roughness = roughness;
      mat.needsUpdate = true;
    }
  }, [baseColor, metalness, roughness]);

  const setupIBL = (renderer, scene) => {
    const rgbeLoader = new RGBELoader();
    const pmremGenerator = new PMREMGenerator(renderer);
    pmremGenerator.compileEquirectangularShader();
    rgbeLoader.load(
      '/textures/hdr/marry_hall_2k.hdr',
      (hdrTexture) => {
        hdrTexture.mapping = EquirectangularReflectionMapping;

        const envMap = pmremGenerator.fromEquirectangular(hdrTexture).texture;

        hdrTexture.dispose();
        pmremGenerator.dispose();

        scene.environment = envMap;
      },
      (err) => {
        console.error('Failed to load HDR texture:', err);
      },
    );
  };

  return (
    <div
      ref={containerRef}
      style={{
        width: '100%',
        height: '100%',
        backgroundImage:
          'linear-gradient(45deg, #ccc 25%, transparent 25%),' +
          'linear-gradient(-45deg, #ccc 25%, transparent 25%),' +
          'linear-gradient(45deg, transparent 75%, #ccc 75%),' +
          'linear-gradient(-45deg, transparent 75%, #ccc 75%)',
        backgroundSize: '20px 20px',
        backgroundPosition: '0 0, 0 10px, 10px -10px, -10px 0px',
        backgroundColor: '#fff',
      }}
    />
  );
};
